################ Lispy: Scheme Interpreter in Python

## (c) Peter Norvig, 2010; See http://norvig.com/lispy.html

################ Symbol, Procedure, Env classes

from __future__ import division

Symbol = str

class Env(dict):
    "An environment: a dict of {'var':val} pairs, with an outer Env."
    def __init__(self, parms=(), args=(), outer=None):
        self.update(zip(parms,args))
        self.outer = outer
    def find(self, var):
        "Find the innermost Env where var appears."
        return self if var in self else self.outer.find(var)

def add_globals(env):
    "Add some Scheme standard procedures to an environment."
    import math, operator as op

    env.update(vars(math)) # sin, sqrt, ...
    
    env.update( {
        '+'     :op.add,
        '-'     :op.sub,
        '*'     :op.mul,
        '/'     :op.div,
        'not'   :op.not_,
        '>'     :op.gt,
        '<'     :op.lt,
        '>='    :op.ge,
        '<='    :op.le,
        '='     :op.eq,
        'cubo'  :lambda x: x * x * x,
        'equal?':op.eq,
        'eq?'   :op.is_,
        'length':len,
        'cons'  :lambda   x , y : [x] + y,
        'car'   :lambda   x : x [0],
        'cdr'   :lambda   x : x [1:],
        'append':op.add,
        'list'  :lambda * x : list (x),
        'list?' :lambda   x : isa (x, list),
        'null?' :lambda   x : x == [],
        'symbol?':lambda  x: isa (x, Symbol),
        'inv' : lambda x : -x
    } )
    return env

global_env = add_globals(Env())

isa = isinstance

################ eval

def eval(x, env=global_env):
    "Evaluate an expression in an environment."
    "x es una lista y (,,,)=x quiere decir que crea una variable de esas"
    "por cada elemento de la lista x..."

    print 'func eval recv ' +  str(type(x))

    if isa(x, Symbol):             # variable reference
        print 'simbolo          -> ' + str(x)
        return env.find(x)[x]
    elif not isa(x, list):         # constant literal
        print 'constant literal -> ' + str(x)
        return x                
    elif x[0] == 'quote':          # (quote exp)
        print 'citacion         -> ' + str(x)
        (_, exp) = x
        return exp
    elif x[0] == 'if':             # (if test conseq alt)
        print 'if               -> ' + str(x)
        (_, test, conseq, alt) = x
        return eval((conseq if eval(test, env) else alt), env)
    elif x[0] == 'set!':           # (set! var exp)
        print 'set!             -> ' + str(x)
        (_, var, exp) = x
        env.find(var)[var] = eval(exp, env)
    elif x[0] == 'define':         # (define var exp)
        print 'define           -> ' + str(x)
        (_, var, exp) = x
        env[var] = eval(exp, env)
    elif x[0] == 'lambda':         # (lambda (var*) exp)
        print 'lambda           -> ' + str(x)
        (_, vars, exp) = x
        return lambda *args: eval(exp, Env(vars, args, env))
    elif x[0] == 'begin':          # (begin exp*)
        print x
        for exp in x[1:]:
            val = eval(exp, env)
        return val
    else:                          # (proc exp*)
        print 'evaluacion de proc -> ' + str (x) 
        exps = [eval(exp, env) for exp in x]
        proc = exps.pop(0)
        return proc(*exps)

################ parse, read, and user interaction

def read(s):
    "Read a Scheme expression from a string."
    return read_from(tokenize(s))

parse = read

def tokenize(s):
    "Convert a string into a list of tokens."
    return s.replace('(',' ( ').replace(')',' ) ').split()

def read_from(tokens):
    "Read an expression from a sequence of tokens."
    if len(tokens) == 0:
        raise SyntaxError('unexpected EOF while reading')
    token = tokens.pop(0)
    if '(' == token:
        L = []
        while tokens[0] != ')':
            L.append(read_from(tokens))
        tokens.pop(0) # pop off ')'
        return L
    elif ')' == token:
        raise SyntaxError('unexpected )')
    else:
        return atom(token)

def atom (token):
    "Numbers become numbers; every other token is a symbol."
    print "func atom recv " + str(token)
    try:
        return int (token)
    except ValueError:
        try:
            return float (token)
        except ValueError:
            return Symbol (token)

def to_string(exp):
    "Convert a Python object back into a Lisp-readable string."
    return '('+' '.join(map(to_string, exp))+')' if isa(exp, list) else str(exp)

def repl(prompt='lis.py> '):
    "A prompt-read-eval-print loop."
    while True:
        val = eval(parse(raw_input(prompt)))
        if val is not None: print to_string(val)
